/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi Play.

    Maxsi Play is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi Play is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi Play.  If not, see <http://www.gnu.org/licenses/>.

	*/ #include "MaxsiPlayContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiPlayContributors.h. For more information please see
	MaxsiPlayContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiPlay.exe
	We watch you because we want to!
	
	Main.cpp
	Rendering a live video feed!

****************************************************************************/

#include "MaxsiEngine.h"
#include <math.h>
#include <io.h>
#include <direct.h>
#include <iostream> 
#include <fcntl.h>
#include <SYS\Stat.h>
#include "Main.h"
 
COLORREF*			CaptureBuffer			=	NULL;
size_t				CaptureBufferSize		=	0;

COLORREF*			LastBuffer				=	NULL;
size_t				LastBufferSize			=	0;

char*				DeltaBuffer				=	NULL;
size_t				DeltaBufferSize			=	0;

char*				CompressedBuffer		=	NULL;
size_t				CompressedBufferSize	=	0;

DWORD_PTR			nThreads				=	0;
RECT				ClientRect;

HDC					hdcScreen				=	NULL;
HDC					hdcScreenCompatible		=	NULL;	
HBITMAP				ScreenshotScaled		=	NULL;

bool				ShouldRun				=	true;

ME_WIN32ENTRYPOINT(MaxsiPlay);

/****************************************************************************

    FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
    PURPOSE: calls initialization function, processes message loop

****************************************************************************/

int WINAPI MaxsiPlay::WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{		
	nThreads	=	GetNumberOfLogicalCPUs();
	
	return BaseClass::WinMain(hInstance,hPrevInstance,lpCmdLine,nCmdShow);
}


/****************************************************************************

    FUNCTION: InitApplication(HANDLE)
    PURPOSE: Initializes window data and registers window class

****************************************************************************/

BOOL MaxsiPlay::InitApplication(
    HANDLE hInstance)
{
    WNDCLASS  wc = {0};

    wc.lpfnWndProc = (WNDPROC) MaxsiPlayProc;
    wc.hInstance = (HINSTANCE)hInstance;
    wc.hIcon = LoadIconA((HINSTANCE)hInstance, "ProductIcon");
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wc.lpszClassName = TEXT("MaxsiPlay");

	if (!RegisterClass(&wc)) { return FALSE; }

	return TRUE;
}


/****************************************************************************

    FUNCTION:  InitInstance(HANDLE, int)
    PURPOSE:  Saves instance handle and creates main window

****************************************************************************/
BOOL MaxsiPlay::InitInstance(
    HINSTANCE hInstance, 
    INT nCmdShow)
{
    LPTSTR lpszText = NULL;
    BOOL bSuccess = TRUE;

    hInst = hInstance;

    hAccTable = LoadAccelerators(hInst, TEXT("MaxsiPlay"));

	// Get the screen size
	DEVMODE Dev;
	BOOL	Result	=	EnumDisplaySettings(NULL,ENUM_CURRENT_SETTINGS,&Dev);
	//int w = Dev.dmPelsWidth;
	//int h = Dev.dmPelsHeight;
	int w = 800;
	int h = 600;
	int x = (Result) ? (int)(Dev.dmPelsWidth - w ) / 2 : (int)CW_USEDEFAULT;
	int y = (Result) ? (int)(Dev.dmPelsHeight - h ) / 2 : (int)CW_USEDEFAULT;

	char*	WindowTitle		=	BuildString(1,"Maxsi Play");

    WindowHWND = CreateWindowA(
        "MaxsiPlay",
        WindowTitle,
        (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX ),
        x, y,
        w, h,
        NULL, NULL, hInstance, NULL );

	delete[] WindowTitle;

	if (!WindowHWND) { return FALSE; }

	ME_Window*	Parent	=	ME_WindowGetPointer(WindowHWND);

	ShowWindow(WindowHWND, nCmdShow);
    bSuccess = UpdateWindow(WindowHWND);

	VideoLoop();

	return bSuccess;
}

/****************************************************************************

    FUNCTION: MainWndProc(HWND, unsigned, WORD, LONG)
    PURPOSE:  Processes messages

****************************************************************************/

LRESULT APIENTRY MaxsiPlay::WindowProc(
    HWND hWnd, 
    UINT message, 
    WPARAM wParam, 
    LPARAM lParam)
{
	switch (message) 
	{
			case WM_PAINT:
				break;

		default:			
			return (BaseClass::WindowProc(hWnd, message, wParam, lParam));
	}
	return (0);

	return BaseClass::WindowProc(hWnd,message,wParam,lParam);
}


DWORD MaxsiPlay::OnThreadRegistered(HWND hWnd)
{
	PostMessage(hWnd,WM_COMMAND,0,0);

	return BaseClass::OnThreadRegistered(hWnd);
}

bool VideoLoop()
{
	// Simply keep recording a video feed!

	while ( ShouldRun )
	{
		COLORREF*	Temp				=	CaptureBuffer;
					CaptureBuffer		=	LastBuffer;
					LastBuffer			=	Temp;

		size_t		Tmp					=	CaptureBufferSize;
					CaptureBufferSize	=	LastBufferSize;
					LastBufferSize		=	Tmp;

		CaptureFeed();
		SubtractFeed();
		DeltaEncodeFeed();
		CompressFeed();
	}

	return true;
}

bool SubtractFeed()
{
	// Make sure we have a delta buffer.

	if ( DeltaBufferSize != 3*ClientRect.right*ClientRect.bottom )
	{
		if ( DeltaBuffer ) { delete[] DeltaBuffer; DeltaBuffer = NULL; }
	}
	if ( DeltaBuffer == NULL )
	{
		DeltaBuffer		=	new char[3*ClientRect.right*ClientRect.bottom];
		DeltaBufferSize	=	3*ClientRect.right*ClientRect.bottom;
	}

	// Copy CaptureFeed into DeltaBuffer.
	size_t NumPixels = ClientRect.right*ClientRect.bottom;
	size_t I3;
	COLORREF Color;

	for ( size_t I = 0; I < NumPixels; I++ )
	{
		I3 = 3*I;
		Color				=	CaptureBuffer[I];
		DeltaBuffer[I3+0]	=	(Color & 0x000000FF);
		DeltaBuffer[I3+1]	=	(Color & 0x0000FF00)>>8;
		DeltaBuffer[I3+2]	=	(Color & 0x00FF0000)>>16;
	}

	// Don't do this if there is no last buffer, or if last buffer isn't the same size.
	if ( LastBuffer != NULL && LastBufferSize == CaptureBufferSize )
	{
		// Subtract the previous frame to generate the delta table!
		for ( size_t I = 0; I < NumPixels; I++ )
		{
			I3 = 3*I;
			Color				=	LastBuffer[I];
			DeltaBuffer[I3+0]	-=	(Color & 0x000000FF);
			DeltaBuffer[I3+1]	-=	(Color & 0x0000FF00)>>8;
			DeltaBuffer[I3+2]	-=	(Color & 0x00FF0000)>>16;
		}
	}

	return true;
}

bool DeltaEncodeFeed()
{
	for ( size_t I = 1; I < DeltaBufferSize; I++ )
	{
		DeltaBuffer[I]	-=	DeltaBuffer[I-1];
	}
	return true;
}

bool CompressFeed()
{
	if ( CompressedBufferSize != compressBound(DeltaBufferSize) )
	{
		if ( CompressedBuffer ) { delete[] CompressedBuffer; CompressedBuffer = NULL; }
	}
	if ( CompressedBuffer == NULL )
	{
		CompressedBuffer		=	new char[compressBound(DeltaBufferSize)];
		CompressedBufferSize	=	compressBound(DeltaBufferSize);
	}

	uLongf	DestLen;

	compress2((Bytef*)CompressedBuffer,&DestLen,(Bytef*)DeltaBuffer,DeltaBufferSize,9);

	return true;
}


bool CaptureFeed()
{
	HWND	hWnd	=	Application.WindowHWND;
    GetClientRect (hWnd, &ClientRect);

	size_t	SizeRequired	=	sizeof(COLORREF)*ClientRect.right*ClientRect.bottom;
	
	if ( CaptureBufferSize != SizeRequired )
	{
		if ( CaptureBuffer ) { delete[] CaptureBuffer; CaptureBuffer = NULL; }
	}

	if ( CaptureBuffer == NULL )
	{
		CaptureBuffer		=	new COLORREF[ClientRect.right*ClientRect.bottom];
		CaptureBufferSize	=	SizeRequired;

		// Make sure the buffer is initialized to zeros. This ensures the alpha channel
		// is always null, which makes compression better.
		size_t BufferSize = ClientRect.right*ClientRect.bottom;

		for ( size_t I = 0; I < BufferSize; I++ )
		{
			CaptureBuffer[I]	=	0;
		}
	}

	// Get read to capture the screen!

	if ( hdcScreen == NULL ) { hdcScreen = GetDC(NULL); }
	if ( hdcScreenCompatible == NULL ) { hdcScreenCompatible = GetDC(Application.WindowHWND); }
	
	if ( ScreenshotScaled == NULL ) { ScreenshotScaled	=	CreateCompatibleBitmap(hdcScreenCompatible, ClientRect.right-ClientRect.left, ClientRect.bottom-ClientRect.top); }

	//SelectObject(hdcScreenCompatible,ScreenshotScaled);

	//This is the best stretch mode
	SetStretchBltMode(hdcScreenCompatible,HALFTONE);

	// Capture the screen!	
	StretchBlt(hdcScreenCompatible, 
		0,0, 
		ClientRect.right, ClientRect.bottom, 
		hdcScreen, 
		0,0,
		GetSystemMetrics (SM_CXSCREEN),
		GetSystemMetrics (SM_CYSCREEN),
		SRCCOPY);
	
	// Get the bits!
	BITMAPINFO	CaptureBufferInfo;
	CaptureBufferInfo.bmiHeader.biBitCount = 32;
	CaptureBufferInfo.bmiHeader.biClrImportant = 0;
	CaptureBufferInfo.bmiHeader.biClrUsed = 0;
	CaptureBufferInfo.bmiHeader.biCompression = BI_RGB;
	CaptureBufferInfo.bmiHeader.biHeight = 0-ClientRect.bottom;
	CaptureBufferInfo.bmiHeader.biPlanes = 1;
	CaptureBufferInfo.bmiHeader.biSize = sizeof(CaptureBufferInfo);
	CaptureBufferInfo.bmiHeader.biSizeImage = (DWORD)SizeRequired;
	CaptureBufferInfo.bmiHeader.biWidth = ClientRect.right;
	CaptureBufferInfo.bmiHeader.biXPelsPerMeter = 0;
	CaptureBufferInfo.bmiHeader.biYPelsPerMeter = 0;

	int		Result	=	GetDIBits(hdcScreenCompatible,ScreenshotScaled,0,ClientRect.bottom,CaptureBuffer,&CaptureBufferInfo, DIB_RGB_COLORS );
	
	//DeleteObject(ScreenshotScaled);
	//ReleaseDC(NULL,hdcScreenCompatible);
	//ReleaseDC(NULL,hdcScreen);
	//DeleteDC(hdcScreenCompatible);

	return true;
}

void RenderFeed()
{
	DWORD	Start	=	GetTickCount();
	for ( size_t I = 0; I < 1000; I++ )
	{
		CaptureFeed();
	}
	DWORD	End		=	GetTickCount();
	DWORD	Delta	=	End-Start;
	float	FPS		=	1000.0f/((float)Delta/1000.f);

	HWND	hWnd	=	Application.WindowHWND;
    GetClientRect (hWnd, &ClientRect);

    PAINTSTRUCT ps  = {0};
	HDC hDC			=	BeginPaint (hWnd, &ps);

	HBITMAP		DrawBackBuffer	=	CreateCompatibleBitmap(hDC, ClientRect.right, ClientRect.bottom);

	BITMAPINFO	DrawBackBufferInfo;
	DrawBackBufferInfo.bmiHeader.biBitCount = 32;
	DrawBackBufferInfo.bmiHeader.biClrImportant = 0;
	DrawBackBufferInfo.bmiHeader.biClrUsed = 0;
	DrawBackBufferInfo.bmiHeader.biCompression = BI_RGB;
	DrawBackBufferInfo.bmiHeader.biHeight = 0-ClientRect.bottom;
	DrawBackBufferInfo.bmiHeader.biPlanes = 1;
	DrawBackBufferInfo.bmiHeader.biSize = sizeof(DrawBackBufferInfo);
	DrawBackBufferInfo.bmiHeader.biSizeImage = BI_RGB;
	DrawBackBufferInfo.bmiHeader.biWidth = ClientRect.right;
	DrawBackBufferInfo.bmiHeader.biXPelsPerMeter = 0;
	DrawBackBufferInfo.bmiHeader.biYPelsPerMeter = 0;

	SetDIBits(hDC,DrawBackBuffer,0,ClientRect.bottom,CaptureBuffer,&DrawBackBufferInfo, DIB_RGB_COLORS );

	BITMAP    Bitmap;

	HDC hDCBits = CreateCompatibleDC(hDC);
	GetObject(DrawBackBuffer, sizeof(BITMAP), (LPSTR)&Bitmap);
	SelectObject(hDCBits, DrawBackBuffer);
	BitBlt(hDC, 0, 0, ClientRect.right, ClientRect.bottom, hDCBits, 0, 0, SRCCOPY);
	DeleteDC(hDCBits);

	DeleteObject(DrawBackBuffer);

    EndPaint (hWnd, &ps);
}